﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using LitJson;
using System.Threading;
using System.IO;
using System.Net;
using System.Web;
using SynoManager.Communication;
using System.Windows.Forms;

namespace SynoManager.DownloadStation
{
    /// <summary>
    /// Class (Service) that represents the Download Station.
    /// </summary>
    public class DownloadStationService : ServiceBase
    {
        private DownloadStationPluginManager plugins;
        internal DownloadRedirector redirector;
        internal DownloadStationClient client;

        public DownloadStationService()
        {
            TestAndUpgradeConfiguration();   
        }

        private void TestAndUpgradeConfiguration()
        {
            System.Reflection.Assembly a = System.Reflection.Assembly.GetExecutingAssembly();
            Version appVersion = a.GetName().Version;
            string appVersionString = appVersion.ToString();

            if (Properties.Settings.Default.ConfigVersion != appVersion.ToString())
            {
                Properties.Settings.Default.Upgrade();
                Properties.Settings.Default.ConfigVersion = appVersionString;
            }
        }

        #region Initialization and Closing
        /// <summary>
        /// Initialize the service.
        /// </summary>
        /// <param name="device">Device for which to initialize</param>
        /// <exception cref="AuthenticationException">Thrown when the user could not be authenticated.</exception>
        /// <exception cref="CommunicationException">Thrown when the device could not be reached.</exception>
        public override void Initialize(SynoDevice device)
        {
            base.Initialize(device);
            plugins = new DownloadStationPluginManager();
            plugins.Initialize(this);
            redirector = new DownloadRedirector(this);
            client = new DownloadStationClient(Device);

            Login();

            StartBackgroundTask(); // Automatic background refresh function
        }

        /// <summary>
        /// Login to the device
        /// </summary>
        /// <exception cref="AuthenticationException">Thrown when the user could not be authenticated.</exception>
        private void Login()
        {
            var result = redirector.Login(Device.Username, Device.Password);
            if (!result.login_success)
                throw new AuthenticationException(Device.Username);
        }

        /// <summary>
        /// Close the service.
        /// </summary>
        public override void Close()
        {
            plugin.Tab.Save();
            Properties.Settings.Default.Save();
            StopBackgroundTask();
            plugins.Close();
            base.Close();
        }
        #endregion

        #region Events
        public event DownloadTaskEventHandler TaskAdded;
        public event DownloadTaskEventHandler TaskRemoved;
        public event EventHandler TasksUpdated;
        public event DownloadTaskEventHandler TaskUpdated;
        public event DownloadTaskEventHandler TaskStatusChanged;
        #endregion

        #region Add Task Functions
        /// <summary>
        /// Add a task to the Download Station.
        /// </summary>
        /// <param name="location">Valid file path or URL.</param>
        public void Add(string location)
        {
            if (File.Exists(location))
            {
                AddFile(location);
                return;
            }
            Uri uri;
            if (Uri.TryCreate(location, UriKind.Absolute, out uri))
            {
                AddUrl(location);
                return;
            }
            throw new InvalidDownloadLocationException(location);
        }

        public void AddFile(string path)
        {
            Debug.WriteLine("Task is file... processing file through plugins", "DownloadStationService");

            ProcessFileArgs args = new ProcessFileArgs(path);
            plugins.Process(args);

            if (args.Cancel)
            {
                Debug.WriteLine("Task processing canceled by plugin", "DownloadStationService");
                return;
            }
            else
            {
                Debug.WriteLineIf(args.Processed, "Task was not processed by any plugin!", "DownloadStationService");
            }
        }

        /// <summary>
        /// Adds a URL to the download queue.
        /// </summary>
        /// <param name="url">The URL.</param>
        /// <remarks>URLs are passed to <see also="IDownloadUrlHandlerPlugin" /> and 
        ///   <seealso "IDownloadUrlProtocolHandlerPlugin"/> plugins. If none of these 
        /// plugins processes the url, it will be send to the download station</remarks>
        public void AddUrl(string url) 
        {
            Debug.WriteLine("Task is url... processing file throught plugins", "DownloadStationService");

            // set base information (can be overridden in plugins)
            // call plugins
            ProcessUrlArgs args = new ProcessUrlArgs(url);
            plugins.Process(args);
            if (args.Cancel)
            {
                Debug.WriteLine("!Task processing canceled by plugin", "DownloadStationService");
                return;
            }

            if (!args.Processed)
            {
                Debug.WriteLine("URL was not processed by any plugin, so we just pass the url...");

                redirector.AddUrl(args.Url.ToString());
            }
        }
        #endregion

        #region Tasks and Handling
        Dictionary<int, DownloadTask> tasks = new Dictionary<int, DownloadTask>();
        /// <summary>
        /// Gets the list of tasks
        /// </summary>
        public IEnumerable<DownloadTask> Tasks
        {
            get { return tasks.Values; }
        }

        /// <summary>
        /// Gets the plugin manager for the Download Station
        /// </summary>
        public DownloadStationPluginManager Plugins
        {
            get { return plugins; }
        }

        /// <summary>
        /// Refresh the lists of tasks
        /// </summary>
        public void Refresh()
        {
            Refresh(true);
        }

        private bool inRefresh;

        /// <summary>
        /// Refresh the list of Download Tasks.
        /// </summary>
        /// <param name="blocking">If true, this function will not return before the set of tasks was renewed.</param>
        void Refresh(bool blocking)
        {
            // TODO: Implement this whole section using a lock/mutex
            Debug.WriteLine("Refresh()", "DownloadStation Service");
            if (inRefresh) // If we're already in a refresh, we can return
            {
                Debug.WriteLine("Already in refresh!", "DownloadStation Service");

                if (!blocking)
                    return;

                while (inRefresh)
                    Thread.Sleep(500); // Yes, it's dirty, that's why this whole section should be locked.
            }

            bool updates = false;
            try
            {
                inRefresh = true; // Set the inRefresh flag

                // List of task-id's that should be refreshed
                // After we've gone through the list of tasks, this list contains the tasks that were removed
                List<int> refreshedTasks = new List<int>(tasks.Keys);

                DownloadRedirector.GetAllResult result;
                try
                {
                    result = redirector.GetAll();
                }
                catch (CommunicationException ex)
                {
                    if (plugin.Host.IsValidConnection)
                    {
                        // Connection was lost...

                        if (!blocking) // we're not in a blocking request (i.e. we're in a background refresh)
                            return; // so just return;
                    }

                    // Exception will be throws unless we have already established a successfull connection
                    // and we're not in a blocking request.
                    throw ex;
                }
                foreach (var item in result.items)
                {
                    int taskId = item.id;

                    if (tasks.ContainsKey(taskId))
                    {
                        // Hey, we've seen this task before!

                        bool updated = tasks[taskId].Update(item); // Updates all properties, and throws Update-event if changed
                        updates = updates | updated;
                        if (updated)
                            OnTaskUpdated(tasks[taskId]);
                        refreshedTasks.Remove(taskId); // Remove the task from the list
                    }
                    else
                    {
                        // Never seen this task id
                        DownloadTask task = new DownloadTask(this, item);
                        tasks.Add(taskId, task);        // Add to the list of tasks
                        OnTaskAdded(task);              // Throw event
                        task.StatusChanged += task_StatusChanged; // Hook status event
                        updates = true;
                    }
                }
                
                // right, now refreshedTasks contains the task id's that are not in the device
                foreach (int taskId in refreshedTasks)
                {
                    updates = true;
                    DownloadTask task = tasks[taskId];
                    // Remove the task and throw the event
                    tasks.Remove(taskId);
                    OnTaskRemoved(task);
                }
            }
            finally
            {
                inRefresh = false;
            }

            if (updates)
                OnTasksUpdated(EventArgs.Empty);
            
            Debug.WriteLine("Refresh finished", "DownloadStation Service");
        }

        void task_StatusChanged(object sender, DownloadTaskStatusEventArgs e)
        {
            OnTaskStatusChanged(e.Task);
        }

        private void OnTasksUpdated(EventArgs e)
        {
            // Since this event is thrown most of the time in a background thread, I added this so we don't have
            // to worry about that on the GUI-side
            if (TasksUpdated != null)
            {
                Delegate[] delegates = TasksUpdated.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] { this, e });
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] { this, e });
                    }
                }
            }
        }

        private void OnTaskAdded(DownloadTask task)
        {
            plugins.OnTaskUpdate(task);
            // Since this event is thrown most of the time in a background thread, I added this so we don't have
            // to worry about that on the GUI-side
            if (TaskAdded != null)
            {
                Delegate[] delegates = TaskAdded.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                }
            }
        }

        private void OnTaskUpdated(DownloadTask task)
        {
            plugins.OnTaskUpdate(task);
            // Since this event is thrown most of the time in a background thread, I added this so we don't have
            // to worry about that on the GUI-side
            if (TaskUpdated != null)
            {
                Delegate[] delegates = TaskUpdated.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                }
            }
        }

        private void OnTaskRemoved(DownloadTask task)
        {
            plugins.OnTaskRemoved(task);
            // Since this event is thrown most of the time in a background thread, I added this so we don't have
            // to worry about that on the GUI-side
            if (TaskRemoved != null)
            {
                Delegate[] delegates = TaskRemoved.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                }
            }
        }

        private void OnTaskStatusChanged(DownloadTask task)
        {
            plugins.OnTaskStateChange(task);
            // Since this event is thrown most of the time in a background thread, I added this so we don't have
            // to worry about that on the GUI-side
            if (TaskStatusChanged != null)
            {
                Delegate[] delegates = TaskStatusChanged.GetInvocationList();
                foreach (Delegate del in delegates)
                {
                    ISynchronizeInvoke target = del.Target as ISynchronizeInvoke;

                    if (target != null && target.InvokeRequired)
                    {
                        // eventhandler moet op een andere thread uitgevoerd worden
                        target.Invoke(del, new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                    else
                    {
                        del.DynamicInvoke(new object[] { this, new DownloadTaskEventArgs(task) });
                    }
                }
            }
        }
        #endregion

        #region Global Queue Functions
        /// <summary>
        /// Pauses all active downloads
        /// </summary>
        public void PauseAll()
        {
            foreach (DownloadTask task in tasks.Values)
            {
                if ((task.State == DownloadState.Downloading) || (task.State == DownloadState.Seeding))
                    task.Pause();
            }

        }

        /// <summary>
        /// Resumes all paused downloads
        /// </summary>
        public void ResumeAll()
        {
            foreach(DownloadTask task in tasks.Values)
            {
                if ((task.State == DownloadState.Paused) || (task.State >= DownloadState.Error))
                    task.Resume();
            }
        }

        /// <summary>
        /// Clear all completed downloads from the list
        /// </summary>
        public void ClearCompleted()
        {
            redirector.Clear();
        }
        #endregion

        #region Background Task
        private bool closing = false;
        private Thread backgroundThread;
        /// <summary>
        /// Start the background task that updates the tasklist on a regular interval.
        /// </summary>
        public void StartBackgroundTask()
        {
            closing = false; // Used in background task
            backgroundThread = new Thread(BackgroundTaskLoop);
            backgroundThread.Start();
        }

        /// <summary>
        /// Stop the background task that updates the tasklist on a regular interval.
        /// </summary>
        public void StopBackgroundTask()
        {
            closing = true;
            backgroundThread.Join(2000); // Give the thread two seconds to complete succesfully            
        }

        int interval = Properties.Settings.Default.BackgroundThreadInterval;
        void BackgroundTaskLoop()
        {
            // Retrieve the interval (in seconds)

            // First sleep this interval
            Thread.Sleep(interval*1000);

            while (!closing)
            {
                Refresh(false);

                // This way we can safely quit this thread, without aborting, by checking each second (timeout is two seconds)
                for(int i=0;(i<interval) && (!closing);i++)
                    Thread.Sleep(1000);
            }
        }
        #endregion

        #region Settings
        /// <summary>
        /// Gets or sets the interval (in seconds) at which the background task automatically refreshes the list of tasks.
        /// </summary>
        public int BackgroundTaskInterval
        {
            get { return interval; }
            set 
            { 
                if (value <= 0)
                    throw new ArgumentOutOfRangeException("value", value, "Value cannot be zero or less");

                interval = value;
                Properties.Settings.Default.BackgroundThreadInterval = value;
            }
        }
        #endregion

        #region Shares
        private ShareInfo shareInfo;
        public ShareInfo Shares
        {
            get
            {
                if (shareInfo == null)
                    shareInfo = new ShareInfo(this);
                return shareInfo;
            }
        }

        public string DownloadFolder
        {
            get { return String.Format(@"\\{0}\{1}", Device.Address, Shares.SelectedShare); }
        }
        #endregion

        #region Configuration
        private DownloadStationConfiguration configuration;
        public DownloadStationConfiguration Configuration
        {
            get
            {
                if (configuration == null)
                    configuration = new DownloadStationConfiguration(this);
                return configuration;
            }
        } 
        #endregion

        #region Download GUI Plugin
        private DownloadGuiPlugin plugin;
        public event EventHandler PluginConnected;

        internal DownloadGuiPlugin Plugin
        {
            get { return plugin; }
            set
            {
                plugin = value;
                if (PluginConnected != null)
                    PluginConnected(this, EventArgs.Empty);
            }
        }
#endregion

        #region Helper Properties
        public long TotalRateDown

        {
            get { return tasks.Sum(t => t.Value.RateAverage); }
        }

        public long TotalRateUp
        {
            get { return tasks.Sum(t => t.Value.UploadRateAverage); }
        }

        #endregion
    }
}
